package ar.edu.itba.pod.legajo48013.cluster;

import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashSet;
import java.util.Set;

import ar.edu.itba.node.Node;
import ar.edu.itba.node.NodeInformation;
import ar.edu.itba.node.api.ClusterAdministration;
import ar.edu.itba.pod.legajo48013.balance.MyAgentsBalancer;
import ar.edu.itba.pod.legajo48013.host.Host;
import ar.edu.itba.pod.legajo48013.rmi.LookupUtil;

public class ClusterImpl extends UnicastRemoteObject implements ClusterAdministration{

	private static final long serialVersionUID = -7679988430797291458L;

	private HashSet<NodeInformation> connectedNodes;
	private Host host;
	private String groupId;
	private String auxGroupId;
	
	public ClusterImpl(Host host, String groupId) throws RemoteException{
		this.host = host;
		this.auxGroupId = groupId; //null if not creating any group
	}
	
	private NodeInformation getNode(){
		return this.host.getNode();
	}
	
	@Override
	public void createGroup() throws RemoteException {
		if(!this.isConnectedToGroup()){
			//TODO synchronize??? Shouldn't happen
			this.connectedNodes = new HashSet<NodeInformation>();
			this.connectedNodes.add(getNode());
			this.groupId = this.auxGroupId;
		}else{
			throw new IllegalStateException("Already in a cluster");
		}
	}

	@Override
	public String getGroupId() throws RemoteException {
		return this.groupId;
	}

	@Override
	public boolean isConnectedToGroup() throws RemoteException {
		return this.groupId!=null;
	}

	@Override
	public void connectToGroup(String host, int port) throws RemoteException, NotBoundException {
		try {
			if(connectedNodes == null){
				connectedNodes = new HashSet<NodeInformation>();
				connectedNodes.add(getNode());
			}
			final Registry registryLook = LocateRegistry.getRegistry(host, port);
			ClusterAdministration cluster = (ClusterAdministration) registryLook.lookup(Node.CLUSTER_COMUNICATION);
			this.groupId = cluster.getGroupId();
			
			synchronized (connectedNodes) { //TODO: this shouldnt happen ?
				this.connectedNodes.addAll(cluster.addNewNode(getNode()));
				for(NodeInformation node: connectedNodes){
					this.host.getUpdates().put(node, System.currentTimeMillis());					
				}
			}
			((MyAgentsBalancer)this.host.getBalancer()).startElection(this.getNode(), System.currentTimeMillis());
		} catch (Exception e) {
			throw new IllegalStateException("No se pudo conseguir la referencia al Cluster que se quiere comunicar", e);
		}
	}

	@Override
	public void disconnectFromGroup(NodeInformation nodeToDisconnect) throws RemoteException, NotBoundException {
		boolean contains = false;
		Set<NodeInformation> copyConnectedNodes = new HashSet<NodeInformation>();
		synchronized (connectedNodes) {
			contains = connectedNodes.remove(nodeToDisconnect);
			copyConnectedNodes.addAll(connectedNodes);
		}
		if(contains){
			for(NodeInformation n: copyConnectedNodes){
				ClusterAdministration nodeStub = (ClusterAdministration) LookupUtil.lookup(this.host, n, Node.CLUSTER_COMUNICATION);
				if(nodeStub!=null){
					nodeStub.disconnectFromGroup(nodeToDisconnect);
				}
			}
		}
		if(getNode().equals(nodeToDisconnect)){
			connectedNodes = null;
		}
	}

	@Override
	public Set<NodeInformation> addNewNode(NodeInformation nodeToAdd) throws RemoteException, NotBoundException {
		host.getUpdates().put(nodeToAdd, System.currentTimeMillis());
		
		if(!connectedNodes.contains(nodeToAdd) && !getNode().equals(nodeToAdd)){
			System.out.println("anadiendo: " + nodeToAdd);
			Set<NodeInformation> copyConnectedNodes = new HashSet<NodeInformation>();
			synchronized (connectedNodes) {
				copyConnectedNodes.addAll(connectedNodes);
				connectedNodes.add(nodeToAdd);
			}
			for(NodeInformation n: copyConnectedNodes){
				System.out.println("haciendole addNewNode a " + n);
				ClusterAdministration nodeStub = (ClusterAdministration) LookupUtil.lookup(host, n, Node.CLUSTER_COMUNICATION);
				if(nodeStub!=null){
					nodeStub.addNewNode(nodeToAdd);
				}
			}
		}
		System.out.println(connectedNodes);
		return connectedNodes;
	}

	@Override
	public Set<NodeInformation> connectedNodes() throws RemoteException {
		return this.connectedNodes;
	}
}
